let configuration = require('../../configuration');
let utils = require('../../utils');
let game = require('../game');

/**
 * properties:
 * compId, cardId, game, x, y, direction: [str,float], width, height, animation: {...}, recordedPos: {x, y},
 * info: {id, name, desc, attack, defend, hp, skill: [{id, name, desc, speed}]}
 */
class Card {
    /**
     * drawConfig = {
     *      x: int, y:int,
     *      size: 'thumb' | 'full',
     *      direction: [
     *          'up' | 'down',
     *          0 // arc(counterclockwise)
     *      ]
     * }
     * @param cardId id of the card, the card info will be loaded by the id
     * @param drawConfig an object
     */
    constructor(cardId, drawConfig) {
        this.cardId = cardId;
        this.__load();
        this.x = drawConfig.x;
        this.y = drawConfig.y;
        this.direction = drawConfig.direction;
        let configName;
        if (drawConfig.size == 'thumb') {
            configName = 'components.card.thumb.';
        } else {
            configName = 'components.card.full.';
        }
        this.width = configuration.get(configName + 'width');
        this.height = configuration.get(configName + 'height');

        this.animations = [];
        this.recordedPos = null;

        this.selectCandidate = false;
        this.selectCandidateDate = null;
        this.selectedAsCandidate = false;

        this.mode = 'normal';
        this.CAN_MOVE_MODE = ['normal', 'inHand'];
        this.SHOW_INFO_MODE = ['normal'];

        game.addEventListener(this);
    }

    __load() {
        configuration.getImage('back', image => {
            this.back = image;
        });
        configuration.getImage(this.cardId, image => {
            this.front = image;
        });
        this.desc = configuration.get('card.desc.' + this.cardId);
        this.info = {
            hp: this.desc.hp,
            attack: this.desc.attack,
            defend: this.desc.defend,
            buff: []
        };
    }

    init(config) {
        this.game = config.game;
        this.compId = config.id;
        this.componentContainer = config.componentContainer;
    }

    includesPoint(x, y) {
        if (this.direction[1] === 0) {
            return x < this.x + this.width / 2 && x > this.x - this.width / 2
                && y < this.y + this.height / 2 && y > this.y - this.height / 2;
        } else {
            return x < this.x + this.height / 2 && x > this.x - this.height
                && y < this.y + this.width / 2 && y > this.y - this.width / 2;
        }
    }

    active() {
        this.game.componentBroadcast('cardActive', this, null);
    }

    inactive() {
        this.game.componentBroadcast('cardInactive', this, null);
    }

    mouseDown() {
        if (this.selectCandidate) {
            if (this.selectedAsCandidate) {
                this.selectedAsCandidate = false;
                this.game.broadcast('cardCandidateCancelSelect', this, null);
            } else {
                this.selectedAsCandidate = true;
                this.game.broadcast('cardCandidateSelect', this, null);
            }
        }
        this.game.componentBroadcast('cardSelect', this, null);
    }

    move(deltaX, deltaY) {
        if (this.CAN_MOVE_MODE.indexOf(this.mode) != -1) {
            this.x += deltaX;
            this.y += deltaY;
        }
    }

    draw(canvas, params) {
        if (this.mode == 'hide') return;
        let ctx = canvas.getContext('2d');
        ctx.lineWidth = 1;
        if (params.isActive) {
            ctx.strokeStyle = 'yellow';
        } else {
            ctx.strokeStyle = 'black';
        }
        if (this.animations.length > 0) {
            let animation = this.animations[0];
            if (!animation.originalMode) {
                animation.originalMode = this.mode;
                this.mode = 'animation';
            }
            let period = animation.period;
            let start = animation.start;
            let now = Date.now();
            let percentage = (now - start) / period;

            if (animation.operation == 'rotate') {
                this.__rotate(animation, percentage);
            } else if (animation.operation == 'flip') {
                this.__flip(animation, percentage);
            } else if (animation.operation == 'rotate-and-flip') {
                this.__rotateAndFlip(animation, percentage);
            } else { // move
                this.__move(animation, percentage);
            }

            if (percentage >= 1) {
                let a = this.animations.shift();
                this.mode = a.originalMode;
                if (a.cb) a.cb();
            }
        }
        this.__simpleDraw(ctx);
    }

    __move(animation, percentage) {
        if (percentage > 1) {
            this.x = animation.x;
            this.y = animation.y;
        } else {
            let deltaX = (animation.x - animation.origin.x) * percentage;
            let deltaY = (animation.y - animation.origin.y) * percentage;
            this.x = animation.origin.x + deltaX;
            this.y = animation.origin.y + deltaY;
        }
    }

    __rotate(animation, percentage) {
        if (percentage >= 1) {
            if (animation.direction == 'clockwise') {
                this.direction[1] = 0;
            } else {
                this.direction[1] = Math.PI / 2;
            }
        } else {
            if (animation.direction == 'clockwise') {
                this.direction[1] = Math.PI / 2 * (1 - percentage);
            } else {
                this.direction[1] = Math.PI / 2 * percentage;
            }
        }
    }

    __flip(animation, percentage) {
        if (percentage > 1) {
            if (animation.origin.direction[0] == 'up') {
                this.direction[0] = 'down';
            } else {
                this.direction[0] = 'up';
            }
            this.width = animation.origin.width;
        } else if (percentage > 0.5) {
            if (animation.origin.direction[0] == 'up') {
                this.direction[0] = 'down';
            } else {
                this.direction[0] = 'up';
            }
            this.width = animation.origin.width * ((percentage - 0.5) * 2);
        } else {
            this.width = animation.origin.width * (1 - percentage * 2);
        }
    }

    __rotateAndFlip(animation, percentage) {
        this.__rotate(animation, percentage * 2);
        if (percentage > 0.5) {
            this.__flip(animation, (percentage - 0.5) * 2);
        }
    }

    __simpleDraw(ctx) {
        // do rotate
        ctx.save();
        ctx.translate(this.x, this.y);
        ctx.rotate(-this.direction[1]);
        let img;
        if (this.direction[0] == 'up') {
            img = this.front;
        } else {
            img = this.back;
        }
        if (img) {
            ctx.drawImage(img, -this.width / 2, -this.height / 2, this.width, this.height);
        }
        ctx.rect(-this.width / 2, -this.height / 2, this.width, this.height);
        ctx.stroke();
        ctx.restore();
        // hp atk def
        if (this.SHOW_INFO_MODE.indexOf(this.mode) !== -1 && !(this.direction[0] == 'down' ||
            (!this.info.hp && !this.info.attack && !this.info.defend))) {

            ctx.beginPath();
            ctx.textAlign = 'center';
            let size = configuration.get('components.card.thumb.fontSize');
            ctx.font = size + 'px courier';
            ctx.fillStyle = 'white';
            if (this.direction[1] === 0) {
                ctx.textBaseline = 'bottom';
                utils.drawTextWithShadow(ctx, '' + this.info.hp, this.x, this.y + this.height / 2 - size);
                utils.drawTextWithShadow(ctx, this.info.attack + '/' + this.info.defend, this.x, this.y + this.height / 2);
            } else {
                ctx.textBaseline = 'bottom';
                utils.drawTextWithShadow(ctx, '' + this.info.hp, this.x, this.y + this.width / 2 - size);
                utils.drawTextWithShadow(ctx, this.info.attack + '/' + this.info.defend, this.x, this.y + this.width / 2);
            }
        }

        this.__drawSelectBorder(ctx);
    }

    __drawSelectBorder(ctx) {
        if (this.selectCandidate || this.selectedAsCandidate) {
            // draw yellow border as selected candidate
            ctx.beginPath();
            ctx.strokeStyle = 'yellow';
            ctx.lineWidth = 6;
            let outPixelX = 10;
            let outPixelY = 5;
            if (this.selectedAsCandidate) {
                if (this.direction[1] === 0) {
                    ctx.rect(this.x - this.width / 2 - outPixelX, this.y - this.height / 2 - outPixelY,
                        this.width + outPixelX * 2, this.height + outPixelY * 2);
                } else {
                    ctx.rect(this.x - this.height / 2 - outPixelX, this.y - this.width / 2 - outPixelY,
                        this.height + outPixelX * 2, this.width + outPixelY * 2);
                }
                ctx.stroke();
            } else {
                let period = 0.5 * 1000;
                let maxStart = 30;
                let percentage = ((Date.now() - this.selectCandidateDate) % period) / period;
                let startX = maxStart * percentage;
                if (this.direction[1] === 0) {
                    utils.drawDashRect(ctx, this.x - this.width / 2 - outPixelX, this.y - this.height / 2 - outPixelY,
                        this.width + outPixelX * 2, this.height + outPixelY * 2, startX, 15, 15);
                } else {
                    utils.drawDashRect(ctx, this.x - this.height / 2 - outPixelX, this.y - this.width / 2 - outPixelY,
                        this.height + outPixelX * 2, this.width + outPixelY * 2, startX, 15, 15);
                }
            }
        }
    }

    moveStart() {
        this.recordedPos = {
            x: this.x,
            y: this.y
        };
    }

    moveEnd() {
        if (this.CAN_MOVE_MODE.indexOf(this.mode) == -1) return;
        let pos = {
            x: this.x,
            y: this.y
        };
        this.x = this.recordedPos.x;
        this.y = this.recordedPos.y;
        this.recordedPos = null;
        this.game.componentBroadcast('cardMoveEnd', this, pos);
    }

    __calculateStartDate() {
        if (this.animations.length == 0) return Date.now();
        let now = Date.now();
        let lastOne = this.animations[this.animations.length - 1];
        if (lastOne.start + lastOne.period < now) return now;
        return lastOne.start + lastOne.period;
    }

    onCardAnimation(sender, animationArgs) { // 'rotate', 'flip', 'rotate-and-flip', 'move'
        if (animationArgs.compId != this.compId) return;
        let operation = animationArgs.operation;
        if (operation == 'straightMove' || operation == 'backMove') {
            let fullPeriod = 0.5 * 1000;
            let timeExceptMove = 0.25;
            let movePeriod = fullPeriod - timeExceptMove;
            if (this.direction[0] == 'down') {
                if (this.direction[1] === 0) {
                    if (operation == 'straightMove') {
                        // flip and move
                        this.onCardAnimation(sender, {compId: this.compId, operation: 'flip', period: timeExceptMove});
                    } // else move
                } else {
                    if (operation == 'straightMove') {
                        // rotate-and-flip and move
                        this.onCardAnimation(sender, {
                            compId: this.compId,
                            operation: 'rotate-and-flip',
                            period: timeExceptMove
                        });
                    } else {
                        // rotate and move
                        this.onCardAnimation(sender, {compId: this.compId, operation: 'rotate', period: timeExceptMove})
                    }
                }
            } else {
                if (this.direction[1] === 0) {
                    if (operation == 'backMove') {
                        // flip and move
                        this.onCardAnimation(sender, {compId: this.compId, operation: 'flip', period: timeExceptMove});
                    } // else move
                } else {
                    if (operation == 'straightMove') {
                        // rotate and move
                        this.onCardAnimation(sender, {
                            compId: this.compId,
                            operation: 'rotate',
                            period: timeExceptMove
                        });
                    } else {
                        // rotate-and-flip and move
                        this.onCardAnimation(sender, {
                            compId: this.compId,
                            operation: 'rotate-and-flip',
                            period: timeExceptMove
                        });
                    }
                }
            }
            this.onCardAnimation(sender, {
                compId: this.compId,
                operation: 'move',
                x: animationArgs.x,
                y: animationArgs.y,
                period: movePeriod,
                cb: () => {
                    this.componentContainer.removeComponent(this);
                    animationArgs.cb();
                }
            });
            return;
        }

        let isVertical = this.direction[1] === 0;

        let animation = {
            operation: operation,
            period: 0.25 * 1000,
            start: this.__calculateStartDate(),
            cb: animationArgs.cb,
            origin: {
                width: this.width,
                height: this.height,
                direction: [this.direction[0], this.direction[1]]
            }
        };
        this.animations.push(animation);
        if (operation == 'rotate' || operation == 'rotate-and-flip') {
            animation.direction = isVertical ? 'counterclockwise' : 'clockwise'
        }
        if (operation == 'move') {
            animation.period = animationArgs.period || 0.1 * 1000;
            animation.x = animationArgs.x;
            animation.y = animationArgs.y;
            animation.origin.x = animationArgs.fromX || this.x;
            animation.origin.y = animationArgs.fromY || this.y;
        }
    }

    onBeforeCardChooseCandidate(sender, filter) {
        if (!filter || Object.keys(filter).length == 0) {
            // no filter, mark as select candidate
            this.selectCandidate = true;
            this.selectCandidateDate = Date.now();
        } else {
            // TODO
        }
    }

    onAfterCardChooseCandidate(sender) {
        this.selectCandidate = false;
        this.selectedAsCandidate = false;
    }

    onComponentRemoved(sender, o) {
        if (o === this) {
            this.game.addEventListener(this);
        }
    }

    onComponentAdded(sender, o) {
        if (o == this) {
            this.game.removeEventListener(this);
        }
    }
}

module.exports = Card;
